/*******************************************************************************
 SimpleScrambler - The implementation of a symmetric algorithm.
 Copyright (C) 2011-2013 Ilya Maximets

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ********************************************************************************
 File: SimpleScrambler.cpp
 Version: 1.3.1  Date: 03.03.2013
 Author(s): Ilya Maximets
 E-mail: ilya.maximets@gmail.com
 *******************************************************************************/

#include<iostream>
#include<fstream>
#include<stdio.h>
#include<cstring>
#include<unistd.h>
#include<termios.h>

using namespace std;

#define VERSION "1.3.1"

unsigned long long S[8][16] =
{
     3,  8, 15,  1, 10,  6,  5, 11, 14, 13,  4,  2,  7,  0,  9, 12,
    15, 12,  2,  7,  9,  0,  5, 10,  1, 11, 14,  8,  6, 13,  3,  4,
     8,  6,  7,  9,  3, 12, 10, 15, 13,  1, 14,  4,  0, 11,  5,  2,
     0, 15, 11,  8, 12,  9,  6,  3, 13,  1,  2,  4, 10,  7,  5, 14,
     1, 15,  8,  3, 12,  0, 11,  6,  2,  5,  4, 10,  9, 14,  7, 13,
    15,  5,  2, 11,  4, 10,  9, 12,  0,  3, 14,  8, 13,  6,  7,  1,
     7,  2, 12,  5,  8,  4,  6, 11, 14,  9,  1, 15, 13,  3, 10,  0,
     1, 13, 15,  0, 14,  8,  2, 11,  7,  4, 12, 10,  9,  3,  5,  6
};

unsigned long long invS[8][16] =
{
    13,  3, 11,  0, 10,  6,  5, 12,  1, 14,  4,  7, 15,  9,  8,  2,
     5,  8,  2, 14, 15,  6, 12,  3, 11,  4,  7,  9,  1, 13, 10,  0,
    12,  9, 15,  4, 11, 14,  1,  2,  0,  3,  6, 13,  5,  8, 10,  7,
     0,  9, 10,  7, 11, 14,  6, 13,  3,  5, 12,  2,  4,  8, 15,  1,
     5,  0,  8,  3, 10,  9,  7, 14,  2, 12, 11,  6,  4, 15, 13,  1,
     8, 15,  2,  9,  4,  1, 13, 14, 11,  6,  5,  3,  7, 12, 10,  0,
    15, 10,  1, 13,  5,  3,  6,  0,  4,  9, 14,  7,  2, 12,  8, 11,
     3,  0,  6, 13,  9, 14, 15,  8,  5, 12, 11,  7, 10,  1,  4,  2
};

unsigned long long cshiftl(unsigned long long k, unsigned long long l, unsigned long long bits)
{
    unsigned long long res = 0;
    res += ( k & ( ((unsigned long long)65535) >> l ) ) << l;
    res += k >> (bits - l);
    return res;
}

unsigned long long cshiftr(unsigned long long k, unsigned long long l, unsigned long long bits)
{
    unsigned long long res = 0;
    res += k >> l;
    res += ( k & ( ((unsigned long long)65535) >> (bits - l)) ) << (bits - l);
    return res;
}

unsigned long long shiftr(unsigned long long k, unsigned long long l, unsigned long long bits)
{
    unsigned long long res = 0;
    res += k >> l;
    return res;
}

unsigned long long shiftl(unsigned long long k, unsigned long long l, unsigned long long bits)
{
    unsigned long long res = 0;
    res += ( k & ( ((unsigned long long)65535) >> l ) ) << l;
    return res;
}

void split(unsigned long long k, unsigned long long& X0, unsigned long long& X1, unsigned long long& X2, unsigned long long&X3, unsigned long long bits)
{
    X0 = 0; X1 = 0; X2 = 0; X3 = 0;
    X0 +=  k & ((unsigned long long)65535);
    X1 += (k & ( ((unsigned long long)65535) << bits )) >> bits;
    X2 += (k & ( ((unsigned long long)65535) << 2*bits )) >> 2*bits;
    X3 += (k & ( ((unsigned long long)65535) << 3*bits )) >> 3*bits;
}

void unsplit(unsigned long long& k, unsigned long long X0, unsigned long long X1, unsigned long long X2, unsigned long long X3, unsigned long long bits)
{
    k = 0;
    k += X0 + ( ((unsigned long long)X1) << bits ) + ( ((unsigned long long)X2) << 2 * bits ) + ( ((unsigned long long)X3) << 3 * bits);
}


unsigned long long Sbox(unsigned long long ind, unsigned long long k)
{
    unsigned long long res = 0;
    for (unsigned long long i = 0; i < 16; i++)
    {
        unsigned long long cur = 0, t = 0;
        cur += ( k & ((unsigned long long)15 << ( i * 4 )) ) >> (i * 4);
        res += cur << ( S[ind][i] * 4 );
    }
    return res;
}

unsigned long long invSbox(unsigned long long ind, unsigned long long k)
{
    unsigned long long res = 0;
    for (unsigned long long i = 0; i < 16; i++)
    {
        unsigned long long cur = 0;
        cur += ( k & ((unsigned long long)15 << ( i * 4 )) ) >> (i * 4);
        res += cur << ( invS[ind][i] * 4 );
    }
    return res;
}


void encrypt(unsigned long long* key, istream& fid, ostream& fout)
{
    unsigned long long X0 = 0, X1 = 0, X2 = 0, X3 = 0;
    unsigned long long init = 0;
    init = invSbox( 3, Sbox( 5, Sbox( 1, key[0] ) ) ^ invSbox( 4, key[1] ) );
    
    while ( !fid.eof() )
    {
        unsigned long long B = 0;
        fid.read( (char*)&B, sizeof(B) );
        B = B ^ init;
        
        for ( int i = 0; i < 32; i++ )
        {
            B = Sbox( i % 8, B ^ key[i] );
            
            split( B, X0, X1, X2, X3, 16 );
            
            X0 = cshiftl( X0, 7, 16 );
            X2 = cshiftl( X2, 3, 16 );
            X1 = X1 ^ X0 ^ X2;
            X3 = X3 ^ X2 ^ shiftl( X0, 3, 16 );
            X1 = cshiftl( X1, 1, 16 );
            X3 = cshiftl( X3, 7, 16 );
            X0 = X0 ^ X1 ^ X3;
            X2 = X2 ^ X3 ^ shiftl( X1, 7, 16 );
            X0 = cshiftl( X0, 5, 16 );
            X2 = cshiftl( X2, 14, 16 );
            
            unsplit( B ,X0, X1, X2, X3, 16 );
        }

        B = Sbox( 7, B ^ key[31] ) ^ key[32];
        init = B;
        fout.write( (char*)&B, sizeof(B) );
    }
    cout << "Encryption complete\n";
    return;
}

void decrypt(unsigned long long* key, istream& fid, ostream& fout)
{
    unsigned long long X0 = 0, X1 = 0, X2 = 0, X3 = 0;
    
    unsigned long long init = 0, init1 = 0;
    init = invSbox( 3, Sbox( 5, Sbox( 1, key[0] ) ) ^ invSbox( 4, key[1]) );

    unsigned long long begin, end, filesize;
    begin = fid.tellg();
    fid.seekg( 0, ios::end );
    end = fid.tellg();
    fid.seekg(0);
    filesize = end - begin;
    
    while ( fid.tellg() < end )
    {
        unsigned long long B = 0;
        fid.read( (char*)&B, sizeof(B) );
        init1 = B;

        B = invSbox( 7, B ^ key[32] ) ^ key[31];

        for ( int i = 31; i >= 0; i--)
        {
            split( B, X0, X1, X2, X3, 16 );
            
            X2 = cshiftr( X2, 14, 16 );
            X0 = cshiftr( X0, 5, 16 );
            X2 = X2 ^ X3 ^ shiftl( X1, 7, 16);
            X0 = X0 ^ X1 ^ X3;
            X3 = cshiftr( X3, 7, 16 );
            X1 = cshiftr( X1, 1, 16);
            X3 = X3 ^ X2 ^ shiftl( X0, 3, 16 );
            X1 = X1 ^ X0 ^ X2;
            X2 = cshiftr( X2, 3, 16 );
            X0 = cshiftr( X0, 7, 16 );
            
            unsplit( B, X0, X1, X2, X3, 16);
            B = invSbox( i % 8, B ) ^ key[i];
            
        }
        
        B = B ^ init;
        init = init1;
        
        unsigned long long tmp = 0;
        tmp = end - fid.tellg();
        if ( tmp > sizeof(B) ){
          tmp = sizeof(B);
        }
        else
        {
         int j = 7;
         while ( !((B & ( ((unsigned long long)255) << j * 8 )) >> j * 8) && j >= 0 )j--;
         tmp = j + 1;
        }
        fout.write( (char*)&B, tmp );
    }
    cout << "Decryption complete\n";
    return;
}

void showdetailedhelp()
{
    cout << " Usage: ./simple-scrambler mode [-i in_filename] [-o out_filename]\n\n";
    cout << "\t-i in_filename\t-\tThe input data will be taken from file in_filename\n";
    cout << "\t-o out_filename\t-\tThe output data will be written to file out_filename\n";
    cout << "\n   mode:\n";
    cout << "\t-e/--encrypt\t-\tThe input data will be encrypted\n";
    cout << "\t-d/--decrypt\t-\tThe input data will be decrypted\n";
    cout << "\t-h/--help\t-\tShow this help\n";
    cout << "\t-v/--version\t-\tShow only version of SimpleScrambler.\n\n";
}

void showinputhelp(int c)
{
    cout << "Usage: ./simple-scrambler mode [-i in_filename] [-o out_filename]\n";
    cout << "mode: -e/--encrypt or -d/--decrypt\n";
    cout << "Use -h/--help option for more information\n";
}

char skey[100000];

int main(int argc, char* argv[])
{
    cout << "\nSimpleScrambler v "VERSION"\n";
    cout << "Copyright (C) 2011 Ilya Maximets\n";
    cout << "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n";
    cout << "This is free software: you are free to change and redistribute it.\n";
    cout << "There is NO WARRANTY, to the extent permitted by law.\n\n";

    
    if ( argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") ){
        showdetailedhelp();
        return 0;
    }
    
    if ( !strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")){
      return 0;
    }
    
    ifstream in;
    ofstream out;
    
    int needFileOut = 0, needFileIn = 0;
    int whattodo = -1;
    
    for ( int i = 1 ; i < argc; i++)
    {
        if ( !strcmp(argv[i], "-e") || !strcmp(argv[i], "--encrypt") )
        {
            whattodo = 0;
            cout << "Mode: encryption\n";
        }
        else if ( !strcmp(argv[i], "-d") || !strcmp(argv[i], "--decrypt") )
        {
            whattodo = 1;
            cout << "Mode: decryption\n";
        }
        else if ( !strcmp(argv[i], "-i") )
        {
            if ( i + 1 ==argc ){
              showinputhelp( __LINE__ );
              return 0;
            }
            in.open( argv[i + 1], ios::in | ios::binary );
            i++;
            needFileIn = 1;
            cout << "Input file name: " << argv[i] << "\n";
        }
        else if ( !strcmp(argv[i], "-o") )
        {
            if ( i + 1 == argc ){
              showinputhelp( __LINE__ );
              return 0;
            }
            out.open( argv[i + 1], ios::out | ios::binary );
            i++;
            needFileOut = 1;
            cout << "Output file name: " << argv[i] << "\n";
        }
    }
    
    if ( whattodo == -1 ){
      showinputhelp( __LINE__ );
      return 0;
    }
    
    cout << "Enter the Key: ";
    
    int c, i, rc, done;

    struct termios orig, now;
    setvbuf( stdout, NULL, _IONBF ,0 );
    tcgetattr( 0, &orig );
    now = orig;
    now.c_lflag &= ~(ISIG|ICANON|ECHO);
    now.c_cc[VMIN] = 1;
    now.c_cc[VTIME] = 2;
    tcsetattr( 0, TCSANOW, &now );
    
    cin >> skey;
    
    while ( strlen(skey) < 1 )
    {
        cout << "\nError: Invalid Key\n";
        cout << "Enter the Key: ";
        cin >> skey;
    }
    
    tcsetattr( 0, TCSANOW, &orig );
    cout << "\n";
    
    unsigned long long key = 0;
    for (unsigned int i = 0; i < 8 && i < strlen(skey); i++)
    {
        key += ((unsigned long long)skey[i]) << (i * 8);
    }
    
    if ( strlen(skey) < 8 ){
      key = Sbox( skey[strlen(skey) / 2] % 8, key) ^ invSbox( 5, Sbox( 3, key ^ (unsigned long long)13) );
    }
    
    for (unsigned int i = 8; i < strlen(skey); i++)
    {
        key = Sbox( (i + 2) % 8, key) ^ invSbox( (i + 2) % 8, (unsigned long long)skey[i] );
    }
    
    unsigned long long keys[33];
    keys[0] = key;
    for ( int i = 1; i < 33; i++)
        keys[i] = Sbox( 7 - i % 8, keys[i - 1]) ^ keys[i - 1];
        
    istream* in_t;
    ostream* out_t;
    if( needFileIn == 1) in_t = &in;
    else in_t = &cin;
    
    if ( needFileOut == 1) out_t = &out;
    else out_t = &cout;
    
    if ( whattodo == 1 ){
      decrypt( keys, *in_t, *out_t );
    }
    else if ( whattodo == 0 ){
      encrypt( keys, *in_t, *out_t );
    }
    else {
      cout << "Unexpected error: O_o\n";
      return 0;
    }
    
 return 0;
}

